/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description: UDK mbuf file
 * Author: -
 * Create: 2021.07.01
 */
#include <inttypes.h>

#include "securec.h"
#include "udk_common.h"
#include "udk_mempool.h"
#include "udk_ops.h"
#include "udk_log.h"
#include "udk_mbuf.h"

/*
 * pktmbuf pool constructor, given as a callback function to udk_mempool_create(),
 * or called directly if using udk_mempool_create_empty()
 */
static void udk_pktmbuf_pool_init(struct udk_mempool *mp, void *opaque_arg)
{
    struct udk_pktmbuf_pool_private *user_mbp_priv = NULL;
    struct udk_pktmbuf_pool_private *mbp_priv = NULL;
    struct udk_pktmbuf_pool_private default_mbp_priv;
    uint16_t roomsz;

    UDK_ASSERT(mp->elt_size >= sizeof(struct udk_mbuf));

    /* If no structure is provided, assume no mbuf private area */
    user_mbp_priv = opaque_arg;
    if (user_mbp_priv == NULL) {
        (void)memset_s(&default_mbp_priv, sizeof(default_mbp_priv), 0, sizeof(default_mbp_priv));
        if (mp->elt_size > sizeof(struct udk_mbuf)) {
            roomsz = (uint16_t)(mp->elt_size - sizeof(struct udk_mbuf));
        } else {
            roomsz = 0;
        }
        default_mbp_priv.mbuf_data_room_size = roomsz;
        user_mbp_priv = &default_mbp_priv;
    }

    UDK_ASSERT(mp->elt_size >= sizeof(struct udk_mbuf) + user_mbp_priv->mbuf_data_room_size +
        user_mbp_priv->mbuf_priv_size);
    UDK_ASSERT(user_mbp_priv->flags == 0);

    mbp_priv = udk_mempool_get_priv(mp);
    (void)memcpy_s(mbp_priv, sizeof(*mbp_priv), user_mbp_priv, sizeof(*mbp_priv));

    return;
}

/*
 * pktmbuf constructor, given as a callback function to udk_mempool_obj_iter() or udk_mempool_create().
 * Set the fields of a packet mbuf to their default values.
 */
void udk_pktmbuf_init(struct udk_mempool *mp, udk_unused void *opaque_arg, void *_m, udk_unused uint32_t i)
{
    struct udk_mbuf *m = _m;
    uint32_t mbuf_size;
    uint16_t priv_size, buf_len;

    priv_size = udk_pktmbuf_priv_size(mp);
    mbuf_size = sizeof(struct udk_mbuf) + priv_size;
    buf_len = udk_pktmbuf_data_room_size(mp);

    UDK_ASSERT(UDK_ALIGN(priv_size, UDK_MBUF_PRIV_ALIGN) == priv_size);
    UDK_ASSERT(mp->elt_size >= mbuf_size);
    UDK_ASSERT(buf_len <= UINT16_MAX);

    (void)memset_s(m, mbuf_size, 0, mbuf_size);
    /* start of buffer is after mbuf structure and priv data */
    m->priv_size = priv_size;
    m->buf_addr = (char *)m + mbuf_size;
    m->buf_iova = udk_mempool_virt2iova(m) + mbuf_size;
    m->buf_len = buf_len;

    /* keep some headroom between start of buffer and data */
    m->data_off = UDK_MIN((uint16_t)UDK_PKTMBUF_HEADROOM, m->buf_len);

    /* init some constant fields */
    m->pool = mp;
    m->nb_segs = 1;
    m->port = UDK_MBUF_INVALID_PORT;
    udk_mbuf_refcnt_set(m, 1);
    m->next = NULL;

    /* internal mbuf */
    m->internal = 1;

    return;
}

struct udk_mbuf *udk_pktmbuf_alloc(struct udk_mempool *mp)
{
    if (strncmp(mp->mz->name, UDK_MEMPOOL_MZ_MAGIC, sizeof(UDK_MEMPOOL_MZ_MAGIC) - 1) != 0) {
        if (g_udk_reg_ops.pktmbuf_alloc != NULL) {
            return g_udk_reg_ops.pktmbuf_alloc(mp);
        } else {
            udk_panic("pktmbuf_alloc is not registered.");
        }
    }

    struct udk_mbuf *m;
    if ((m = udk_mbuf_raw_alloc(mp)) != NULL) {
        udk_pktmbuf_reset(m);
    }

    return m;
}

int udk_pktmbuf_alloc_bulk(struct udk_mempool *mp, struct udk_mbuf **mbufs, uint32_t count)
{
    if (strncmp(mp->mz->name, UDK_MEMPOOL_MZ_MAGIC, sizeof(UDK_MEMPOOL_MZ_MAGIC) - 1) != 0) {
        if (g_udk_reg_ops.pktmbuf_alloc_bulk != NULL) {
            return g_udk_reg_ops.pktmbuf_alloc_bulk(mp, mbufs, count);
        } else {
            udk_panic("pktmbuf_alloc_bulk is not registered.");
        }
    }

    uint32_t idx = 0;
    uint32_t unaligned;
    int rc;

    rc = udk_mempool_get_bulk(mp, (void **)mbufs, count);
    if (unlikely(rc != 0)) {
        return rc;
    }

    unaligned = count % 0x4;
    while (unaligned != 0) {
        UDK_MBUF_RAW_ALLOC_CHECK(mbufs[idx]);
        udk_pktmbuf_reset(mbufs[idx]);
        idx++;
        unaligned--;
    }

    while (idx != count) {
        UDK_MBUF_RAW_ALLOC_CHECK(mbufs[idx]);
        udk_pktmbuf_reset(mbufs[idx]);
        idx++;
        UDK_MBUF_RAW_ALLOC_CHECK(mbufs[idx]);
        udk_pktmbuf_reset(mbufs[idx]);
        idx++;
        UDK_MBUF_RAW_ALLOC_CHECK(mbufs[idx]);
        udk_pktmbuf_reset(mbufs[idx]);
        idx++;
        UDK_MBUF_RAW_ALLOC_CHECK(mbufs[idx]);
        udk_pktmbuf_reset(mbufs[idx]);
        idx++;
    }

    return 0;
}

void udk_pktmbuf_free(struct udk_mbuf *m)
{
    if (m != NULL && m->internal != 1) {
        if (g_udk_reg_ops.pktmbuf_free != NULL) {
            g_udk_reg_ops.pktmbuf_free(m);
            return;
        } else {
            udk_panic("pktmbuf_free is not registered.");
        }
    }

    struct udk_mbuf *m_next = NULL;

    udk_mbuf_sanity_check(m, 1);

    while (m != NULL) {
        m_next = m->next;
        udk_pktmbuf_free_seg(m);
        m = m_next;
    }

    return;
}

static const char *udk_mbuf_best_mempool_ops(void)
{
    /* Use the compile time config pool by default */
    return UDK_MBUF_DEFAULT_MEMPOOL_OPS;
}

struct udk_mempool *udk_pktmbuf_pool_create_by_ops(const char *name, uint32_t n, uint32_t cache_size,
    uint16_t priv_size, uint16_t data_room_size, int socket_id, const char *ops_name, uint32_t flags)
{
    struct udk_mempool *mp = NULL;
    struct udk_pktmbuf_pool_private mbp_priv;
    const char *mp_ops_name = ops_name;
    unsigned elt_size;
    int ret;

    if (UDK_ALIGN(priv_size, UDK_MBUF_PRIV_ALIGN) != priv_size) {
        UDK_LOG(ERR, MBUF, "mbuf priv_size=%u is not aligned\n", priv_size);
        return NULL;
    }
    elt_size = sizeof(struct udk_mbuf) + (uint32_t)priv_size + (uint32_t)data_room_size;
    (void)memset_s(&mbp_priv, sizeof(mbp_priv), 0, sizeof(mbp_priv));
    mbp_priv.mbuf_data_room_size = data_room_size;
    mbp_priv.mbuf_priv_size = priv_size;

    mp = udk_mempool_create_empty(name, n, elt_size, cache_size,
        sizeof(struct udk_pktmbuf_pool_private), socket_id, flags);
    if (mp == NULL) {
        return NULL;
    }

    if (mp_ops_name == NULL) {
        mp_ops_name = udk_mbuf_best_mempool_ops();
    }

    ret = udk_mempool_set_ops_byname(mp, mp_ops_name, NULL);
    if (ret != 0) {
        UDK_LOG(ERR, MBUF, "error setting mempool handler\n");
        udk_mempool_free(mp);
        return NULL;
    }
    udk_pktmbuf_pool_init(mp, &mbp_priv);

    ret = udk_mempool_populate_default(mp);
    if (ret < 0) {
        udk_mempool_free(mp);
        return NULL;
    }

    (void)udk_mempool_obj_iter(mp, udk_pktmbuf_init, NULL);

    return mp;
}

struct udk_mempool *udk_pktmbuf_pool_create(const char *name, uint32_t n, uint32_t cache_size, uint16_t priv_size,
    uint16_t data_room_size, int socket_id, uint32_t flags)
{
    if (g_udk_reg_ops.pktmbuf_pool_create != NULL) {
        return g_udk_reg_ops.pktmbuf_pool_create(name, n, cache_size, priv_size, data_room_size, socket_id);
    }

    return udk_pktmbuf_pool_create_by_ops(name, n, cache_size, priv_size,
        data_room_size, socket_id, NULL, flags);
}

static int udk_mbuf_check(const struct udk_mbuf *m, int is_header, const char **reason)
{
    uint32_t pkt_len, nb_segs;
    uint16_t refcnt;

    if (m == NULL) {
        *reason = "Mbuf is NULL!";
        return -1;
    }

    if (!m->pool) {
        *reason = "Bad mbuf pool!";
        return -1;
    }

    if (!m->buf_iova) {
        *reason = "Bad IO addr!";
        return -1;
    }

    if (!m->buf_addr) {
        *reason = "Bad virt addr!";
        return -1;
    }

    refcnt = udk_mbuf_refcnt_read(m);
    if (refcnt == 0 || refcnt == UINT16_MAX) {
        *reason = "Bad ref cnt!";
        return -1;
    }

    if (is_header == 0) {
        return 0;
    }

    if (m->data_len > m->pkt_len) {
        *reason = "Bad data_len!";
        return -1;
    }

    pkt_len = m->pkt_len;
    nb_segs = m->nb_segs;

    do {
        if (m->data_off > m->buf_len) {
            *reason = "Data offset too big!";
            return -1;
        }
        if (m->data_off + m->data_len > m->buf_len) {
            *reason = "Data length too big!";
            return -1;
        }
        pkt_len -= m->data_len;
        nb_segs -= 1;
    } while ((m = m->next) != NULL);

    if (nb_segs != 0 || pkt_len != 0) {
        *reason = (nb_segs != 0) ? "Bad nb_segs!" : "Bad pkt_len!";
        return -1;
    }

    return 0;
}

void mbuf_sanity_check(const struct udk_mbuf *m, int is_header)
{
    const char *reason = NULL;
    if (udk_mbuf_check(m, is_header, &reason) != 0) {
        udk_panic("%s\n", reason);
    }
}

void udk_pktmbuf_dump(FILE *f, const struct udk_mbuf *m, uint32_t dump_len)
{
    uint32_t len;
    uint16_t nb_segs;

    udk_mbuf_sanity_check(m, 1);

    (void)fprintf(f, "dump mbuf at 0x%lx, iova=%#" PRIx64 ", buf_len=%u\n", udk_get_ptr(m), m->buf_iova, m->buf_len);
    (void)fprintf(f, "  pkt_len=%u, ol_flags=%#" PRIx64 ", nb_segs=%u, port=%u",
        m->pkt_len, m->ol_flags, m->nb_segs, m->port);

    if (m->ol_flags & (PKT_RX_VLAN | PKT_TX_VLAN)) {
        (void)fprintf(f, ", vlan_tci=%u", m->vlan_tci);
    }

    (void)fprintf(f, ", ptype=%u\n", m->packet_type);

    nb_segs = m->nb_segs;
    while (m != NULL && nb_segs != 0) {
        udk_mbuf_sanity_check(m, 0);

        (void)fprintf(f, "  segment at 0x%lx, data=0x%lx, len=%u, off=%u, refcnt=%u\n", udk_get_ptr(m),
            udk_get_ptr(udk_pktmbuf_mtod(m, void *)), m->data_len, m->data_off, udk_mbuf_refcnt_read(m));

        len = dump_len;
        if (len > m->data_len) {
            len = m->data_len;
        }

        if (len != 0) {
            udk_hexdump(f, NULL, udk_pktmbuf_mtod(m, void *), len);
        }

        dump_len -= len;
        m = m->next;
        nb_segs--;
    }
}
